<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    
    <title>暗黑模式适配 | ApocalypseBlog</title>
    <canvas id="header_canvas"style="position:absolute;bottom:0"></canvas> 
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    

    

    

    

    
<link rel="stylesheet" href="/dist/build.css?v=1.14.0.css">

    <script src="/javascripts/bubble.js"></script>
    <script>
        window.isPost = true
        window.aomori = {
            
            
        }
        window.aomori_logo_typed_animated = false
        window.aomori_search_algolia = false

    </script>
<script>
    ((window.gitter = {}).chat = {}).options = {
      room: 'ApocalypseBlog/Apocalypse'
    };
  </script>
  <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<meta name="generator" content="Hexo 5.3.0"></head>
<!--DaoVoice服务http://blog.daovoice.io/-->

    <script>(function (i, s, o, g, r, a, m) {
        i['DaoVoiceObject'] = r;
        i[r] = i[r] ||
          function () {
            (i[r].q = i[r].q || []).push(arguments);
          };
        i[r].l = 1 * new Date();
        a = s.createElement(o);
        m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        a.charset = 'utf-8';
        m.parentNode.insertBefore(a, m);
      })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/b00f5052.js", 'daovoice');
      daovoice('init', {
        app_id: "b00f5052",
      });
      daovoice('update');
    </script>
  
<body>

    <div class="container">
    <header class="header">
        <div class="header-type">
            
            <div class="header-type-inner">
                
                    <a class="header-type-title" href="/">ApocalypseBlog</a>
                
    
                
            </div>
        </div>
        <div class="header-menu">
            <div class="header-menu-inner">
                
            </div>
            <div class="header-menu-social">
                
            </div>
        </div>

        <div class="header-menu-mobile">
            <div class="header-menu-mobile-inner" id="mobile-menu-open">
                <i class="icon icon-menu"></i>
            </div>
        </div>
    </header>

    <div class="header-menu-mobile-menu">
        <div class="header-menu-mobile-menu-bg"></div>
        <div class="header-menu-mobile-menu-wrap">
            <div class="header-menu-mobile-menu-inner">
                <div class="header-menu-mobile-menu-close" id="mobile-menu-close">
                    <i class="icon icon-cross"></i>
                </div>
                <div class="header-menu-mobile-menu-list">
                    
                </div>
            </div>
        </div>
    </div>

</div>

    <div class="container">
        <div class="main">
            <section class="inner">
                <section class="inner-main">
                    <div class="post">
    <article id="post-ckk6o6avr000t7kny8vt3dxt5" class="article article-type-post" itemscope
    itemprop="blogPost">

    <div class="article-inner">

        
          
        
        
        

        
        <header class="article-header">
            
  
    <h1 class="article-title" itemprop="name">
      暗黑模式适配
    </h1>
  

        </header>
        

        <div class="article-more-info article-more-info-post hairline">

            <div class="article-date">
  <time datetime="2020-06-13T17:32:15.000Z" itemprop="datePublished">2020-06-14</time>
</div>

            

            

            

        </div>

        <div class="article-entry post-inner-html hairline" itemprop="articleBody">
            <h1 id="暗黑模式"><a href="#暗黑模式" class="headerlink" title="暗黑模式"></a>暗黑模式</h1><p>在 Android 10 里，Dark theme 暗黑模式得到了系统级的支持。 暗黑模式不仅酷炫，而且有降低屏幕耗电、在光线较暗的环境中使用更舒适等好处。 今天带大家看一下如何适配暗黑模式，本文会从以下几点进行介绍：</p>
<ul>
<li>动态开启暗黑模式</li>
<li>使用 DayNight 适配暗黑模式</li>
<li>使用 Force Dark 适配暗黑模式</li>
<li>Force Dark 系统源码解析</li>
<li>适配流程建议</li>
</ul>
<p>相信本文会让你对暗黑模式有一个更全面的了解。</p>
<a id="more"></a>

<h1 id="动态开启"><a href="#动态开启" class="headerlink" title="动态开启"></a>动态开启</h1><p>在 Android 10 系统设置里增加了暗黑模式的开关，但除了系统设置，我们也可以自己动态开启。 假如我们项目里面有一个按钮用来开关暗黑模式，可以这样做：</p>
<pre><code>btn.setOnClickListener &#123;
    if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) &#123;
        // 关闭暗黑模式
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
    &#125; else &#123;
        // 开启暗黑模式
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
    &#125;
&#125;
</code></pre>
<p>如果当前开启了暗黑模式就关掉，反之开启。 你可能还看过另一种 delegate.localNightMode 的写法，同样也是可以生效的，它们的区别在于作用范围不同：</p>
<pre><code>// 作用于当前项目的所有组件
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 
// 只作用于当前组件
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES              
</code></pre>
<p>另外需要注意的是，在默认情况下，设置暗黑模式会重走 Activity 生命周期，需要重新渲染整个页面，所以不要在 onCreate 里直接设置。 如果不想重走生命周期，可以给 Activity 配置 android:configChanges=”uiMode”，但这样一来就需要在 onConfigurationChanged() 方法里进行手动适配。</p>
<h2 id="NightMode"><a href="#NightMode" class="headerlink" title="NightMode"></a>NightMode</h2><p>上面用到了 YES 和 NO 两种暗黑的状态，但其实还不止这两种，暗黑模式一共有这几种状态：</p>
<ul>
<li>MODE_NIGHT_FOLLOW_SYSTEM 跟随系统设置</li>
<li>MODE_NIGHT_NO 关闭暗黑模式</li>
<li>MODE_NIGHT_YES 开启暗黑模式</li>
<li>MODE_NIGHT_AUTO_BATTERY 系统进入省电模式时，开启暗黑模式</li>
<li>MODE_NIGHT_UNSPECIFIED 未指定，默认值</li>
</ul>
<p>由于很多定制系统对省电模式进行了魔改，所以使用 MODE_NIGHT_AUTO_BATTERY 不一定会生效。 另外，当 DefaultNightMode 和 LocalNightMode 都是默认值 MODE_NIGHT_UNSPECIFIED 的时候，会作 MODE_NIGHT_FOLLOW_SYSTEM 跟随系统处理。</p>
<h1 id="DayNight"><a href="#DayNight" class="headerlink" title="DayNight"></a>DayNight</h1><p>下面要开始对暗黑模式进行适配啦。我们使用 Android Studio 的 Basic Activity 模板创建一个项目，对它进行暗黑模式适配的改造。</p>
<h2 id="DayNight-主题适配"><a href="#DayNight-主题适配" class="headerlink" title="DayNight 主题适配"></a>DayNight 主题适配</h2><p>第一步，找到当前项目使用的主题，将默认使用的 Theme.AppCompat.Light 主题修改为 Theme.AppCompat.DayNight：</p>
<pre><code>&lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.DayNight&quot;&gt;
    &lt;item name=&quot;colorPrimary&quot;&gt;@color/colorPrimary&lt;/item&gt;
    &lt;item name=&quot;colorPrimaryDark&quot;&gt;@color/colorPrimaryDark&lt;/item&gt;
    &lt;item name=&quot;colorAccent&quot;&gt;@color/colorAccent&lt;/item&gt;
&lt;/style&gt;
</code></pre>
<p>第二步，没有第二步了，现在这个项目已经支持暗黑模式了，开启暗黑模式就能看到效果：</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd485a20f96?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>是不是很简单，但直觉告诉我们肯定没有这么简单。</p>
<h2 id="硬编码"><a href="#硬编码" class="headerlink" title="硬编码"></a>硬编码</h2><p>我们进入 MainActivity 的布局文件 activity_main，可以发现这里面是完全没有使用硬编码的。 什么叫硬编码？就是我们平时所说的「写死」。要是我们写死了一个色值，暗黑模式还能生效吗？ 马上试一下，我们给根布局写死一个白色背景 android:background=”#FFFFFF”，切换暗黑模式就变成了这样：</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd487a61d7b?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>可以看到，在写死色值的情况下暗黑模式就失效了。下面看看对于自定义的色值，要如何适配。</p>
<h2 id="value-night"><a href="#value-night" class="headerlink" title="value-night"></a>value-night</h2><p>在 colors.xml 里添加一个配置颜色，比如：</p>
<pre><code>&lt;color name=&quot;color_bg&quot;&gt;#FFFFFF&lt;/color&gt;
</code></pre>
<p>这个是在普通模式下使用的色值，为了适配暗黑模式，还需要一个在暗黑模式下对应的色值。 新建 values-night 目录，并把对应色值配置到这个目录下的 colors.xml 文件。</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd487e14870?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>将根布局的背景颜色修改为 color_bg，这样就能使用我们自己想要的颜色进行适配了：</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd48cca9fad?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>在暗黑模式下，系统会优先从 night 后缀的目录下找到对应的资源配置。 以上就是使用 DayNight 主题进行暗黑模式适配的全部内容了。</p>
<h2 id="DayNight-弊端"><a href="#DayNight-弊端" class="headerlink" title="DayNight 弊端"></a>DayNight 弊端</h2><p>一些关于 Android 10 暗黑模式适配的文章到这里就结束了，但其实 DayNight 主题并不是 Android 10 新增的东西，它早在 Android 6.0 就已经出现。虽然它涉及的内容不多，但大家可能也发现了，在实际项目中它的可操作性并不高。 首先，使用这种适配方式，要求我们整个项目所有的色值都不能使用硬编码，要做到这一点已经很不容易了，很多项目连统一的设计规范都很难做到。再退一步讲，就算我们所有色值都是使用 xml 配置的，但 colors.xml 里配置了成百上千个色值，我们需要对所有这些色值配置一个对应的暗黑色值，并且要确保它们在暗黑模式下能比较美观的展示。 所以，除非项目本身已经有一套严格的设计规范并且严格执行了，否则使用 DayNight 主题适配暗黑模式基本是不具有可操作性的。 Android 10 新增的当然不只是一个暗黑模式的开关而已，下面我们看一下 Android 10 有什么新特性供我们适配。</p>
<h1 id="Force-Dark"><a href="#Force-Dark" class="headerlink" title="Force Dark"></a>Force Dark</h1><p>其实我们的需求很明确，就是使用了硬编码也能被适配成暗黑模式。Android 10 新增的 Force Dark 强制暗黑就实现了这个功能。</p>
<h2 id="forceDarkAllowed"><a href="#forceDarkAllowed" class="headerlink" title="forceDarkAllowed"></a>forceDarkAllowed</h2><p>还是回到刚才的项目，把背景写死白色，再次来到 styles.xml 的主题配置。这次我们不用 DayNight 主题了，把配置改成如下：</p>
<pre><code>&lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.Light&quot;&gt;
    &lt;item name=&quot;colorPrimary&quot;&gt;@color/colorPrimary&lt;/item&gt;
    &lt;item name=&quot;colorPrimaryDark&quot;&gt;@color/colorPrimaryDark&lt;/item&gt;
    &lt;item name=&quot;colorAccent&quot;&gt;@color/colorAccent&lt;/item&gt;
    &lt;item name=&quot;android:forceDarkAllowed&quot;&gt;true&lt;/item&gt;
&lt;/style&gt;
</code></pre>
<p>我们把主题换回 Light 亮色主题，至于为什么要用 Light 后面源码部分还会再讲到 另外，重点来了，这里还增加了一个 forceDarkAllowed 的配置，这是 compileSdkVersion 升级到 29 新增的配置，按字面意思就是「开启强制暗黑」。 这样就已经完成配置了，在 Android 10 的机器上运行一下，切换暗黑模式，记住这次的背景是写死白色的：</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd48e65b69c?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>背景被强制转换成黑色了，细心的还会发现，右下角按钮的背景颜色也变深了。 Force Dark 这么暴力，连我们写死的色值都改了，虽然方便，但这也给我们一种不安全感。 要是 Force Dark 适配出来的颜色不是我们想要的怎么办？我们还能自定义暗黑色值吗？也是可以的。</p>
<h2 id="Force-Dark-自定义适配"><a href="#Force-Dark-自定义适配" class="headerlink" title="Force Dark 自定义适配"></a>Force Dark 自定义适配</h2><p>除了主题新增了 forceDarkAllowed 这个配置，View 里面也有。 如果某个 View 的需要使用自定义色值适配暗黑模式，我们需要对这个 View 添加这个配置，让 Force Dark 排除它：</p>
<pre><code>android:forceDarkAllowed=&quot;false&quot;
</code></pre>
<p>然后在代码里根据当前是否处于暗黑模式，对色值进行动态设置。 对于 View 的 forceDarkAllowed，有几点需要注意：</p>
<ul>
<li>在 View 中使用这个配置的前提是，当前主题开启了 Force Dark</li>
<li>默认值是 true，所以设为 true 和不设是一样的</li>
<li>作用范围是当前 View 以及它所有的子 View</li>
</ul>
<p>综上可以看出，其实目前并没有很好的 Force Dark 自定义方案。好在 Force Dark 的整体效果没什么大问题，就算要自定义，我们也尽量只对子 View 进行自定义。</p>
<h1 id="Force-Dark-源码解析"><a href="#Force-Dark-源码解析" class="headerlink" title="Force Dark 源码解析"></a>Force Dark 源码解析</h1><p>下面我们看一下源码，看看系统在暗黑模式下是如何对颜色进行转换的。 这里仅展示几个关键源码片段，它们之间是如何调用的就不赘述啦。</p>
<h2 id="updateForceDarkMode"><a href="#updateForceDarkMode" class="headerlink" title="updateForceDarkMode"></a>updateForceDarkMode</h2><p>看源码首先我们要找到入口，入口就是主题的 forceDarkAllowed 配置，搜索一下可以发现这个配置会在 ViewRootImpl 被用到。 相关的说明已经用注释写在代码里了。</p>
<pre><code>// android.view.ViewRootImpl.java

private void updateForceDarkMode() &#123;
    if (mAttachInfo.mThreadedRenderer == null) return;

    // 判断当前是否处于暗黑模式
    boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;

    if (useAutoDark) &#123;
        // 这个是被用来作为默认值用的，这里先不管它，我们后面还会讲到。
        boolean forceDarkAllowedDefault = SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
        // 判断当前是否为 Light 主题，这也是为什么我们前面要使用 Light 主题。这也很好理解，只有当前主题是亮色的时候，才需要进行暗黑的处理。
        // 判断当前是否允许开启强制暗黑，我们就是靠它找到这个地方的。
        useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
                &amp;&amp; a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
        a.recycle();
    &#125;

    if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) &#123;
        // TODO: Don&#39;t require regenerating all display lists to apply this setting
        invalidateWorld(mView);
    &#125;
&#125;
</code></pre>
<p>总结一下，根据这个方法我们可以知道，Force Dark 生效有三个条件：</p>
<ul>
<li>处于暗黑模式</li>
<li>使用了 Light 亮色主题</li>
<li>允许使用 Force Dark</li>
</ul>
<p>源码再跟下去，发现调用了 Native 代码。</p>
<h2 id="handleForceDark"><a href="#handleForceDark" class="headerlink" title="handleForceDark"></a>handleForceDark</h2><p>下一个关键代码是 RenderNode 的 handleForceDark 函数。RenderNode 是绘制节点，一个 View 可以有多个绘制节点，比如一个 TextView 的文字部分是一个绘制节点，它设置的背景也是一个绘制节点。看一下这个函数做了什么。</p>
<pre><code>// frameworks/base/libs/hwui/RenderNode.cpp

void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) &#123;
    if (CC_LIKELY(!info || info-&gt;disableForceDark)) &#123;
        return;
    &#125;
    // 这个函数看似有点复杂，但其实我们只需要关注 usage 这个参数。
    // usage 有两个取值，Foreground 前景和 Background 背景。
    auto usage = usageHint();
    const auto&amp; children = mDisplayList-&gt;mChildNodes;
    if (mDisplayList-&gt;hasText()) &#123;
        // 如果当前节点 hasText() 含有文字，那它就是一个 Foreground 前景
        usage = UsageHint::Foreground;
    &#125;
    // 下面的判断都是设为 Background 背景
    if (usage == UsageHint::Unknown) &#123;
        if (children.size() &gt; 1) &#123;
            usage = UsageHint::Background;
        &#125; else if (children.size() == 1 &amp;&amp;
                children.front().getRenderNode()-&gt;usageHint() !=
                        UsageHint::Background) &#123;
            usage = UsageHint::Background;
        &#125;
    &#125;
    if (children.size() &gt; 1) &#123;
        // Crude overlap check
        SkRect drawn = SkRect::MakeEmpty();
        for (auto iter = children.rbegin(); iter != children.rend(); ++iter) &#123;
            const auto&amp; child = iter-&gt;getRenderNode();
            // We use stagingProperties here because we haven&#39;t yet sync&#39;d the children
            SkRect bounds = SkRect::MakeXYWH(child-&gt;stagingProperties().getX(), child-&gt;stagingProperties().getY(),
                    child-&gt;stagingProperties().getWidth(), child-&gt;stagingProperties().getHeight());
            if (bounds.contains(drawn)) &#123;
                // This contains everything drawn after it, so make it a background
                child-&gt;setUsageHint(UsageHint::Background);
            &#125;
            drawn.join(bounds);
        &#125;
    &#125;
    // 根据分类，如果是背景会被设为 Dark 深色，否则是 Light 亮色。
    mDisplayList-&gt;mDisplayList.applyColorTransform(
            usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
&#125;
</code></pre>
<p>这个函数做的就是对当前绘制节点进行 Foreground 还是 Background 的分类。 为了保证文字的可视度，需要保证一定的对比度，在背景切换成深色的情况下，需要把文字部分切换成亮色。</p>
<h2 id="transformColor"><a href="#transformColor" class="headerlink" title="transformColor"></a>transformColor</h2><p>根据分好的颜色类型，会进入 CanvasTransform 对颜色进行转换处理。这里也是 Force Dark 最核心的地方了。</p>
<pre><code>// frameworks/base/libs/hwui/CanvasTransform.cpp

static SkColor transformColor(ColorTransform transform, SkColor color) &#123;
    switch (transform) &#123;
        case ColorTransform::Light:
            // 转换为亮色
            return makeLight(color);
        case ColorTransform::Dark:
            // 转换为暗色
            return makeDark(color);
        default:
            return color;
    &#125;
&#125;
</code></pre>
<p>根据类型调用了对应的函数转换颜色，我们看一下 makeDark 吧。</p>
<pre><code>static SkColor makeDark(SkColor color) &#123;
    Lab lab = sRGBToLab(color);
    float invertedL = std::min(110 - lab.L, 100.0f);
    if (invertedL &lt; lab.L) &#123;
        lab.L = invertedL;
        return LabToSRGB(lab, SkColorGetA(color));
    &#125; else &#123;
        return color;
    &#125;
&#125;
</code></pre>
<p>这里把 RGB 色值转换成了 Lab 的格式。 Lab 格式含有 L、a、b 三个参数，ab 对应色彩学上的两个维度，不用管它，我们要关注的是里面的 L。 L 就是亮度，它的取值范围是 0 - 100，数值越小颜色就越暗，反之就越亮。这篇文章封面的安卓机器人右边颜色就是降低亮度后的效果。 回到代码来，这里用 110 减去当前亮度，可以说是对亮度做了取反。至于为什么是用 110 而不是用 100，我猜测是为了避免使用纯黑色。 在官方<a target="_blank" rel="noopener" href="https://material.io/design/color/dark-theme.html#properties">暗黑模式设计规范</a>可以看到，建议使用深灰色作为背景，而不是用纯黑色。</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd4abaea406?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>最后比对取反的色值和原色值的亮度，将较暗的那个色值返回。 makeLight 函数也是类似的。</p>
<pre><code>static SkColor makeLight(SkColor color) &#123;
    Lab lab = sRGBToLab(color);
    float invertedL = std::min(110 - lab.L, 100.0f);
    if (invertedL &gt; lab.L) &#123;
        lab.L = invertedL;
        return LabToSRGB(lab, SkColorGetA(color));
    &#125; else &#123;
        return color;
    &#125;
&#125;
</code></pre>
<p>所以到这里我们发现，其实 Force Dark 强制暗黑转换颜色的规则，或者说是它的本质，就是<strong>亮度取反</strong>。</p>
<h1 id="适配流程建议"><a href="#适配流程建议" class="headerlink" title="适配流程建议"></a>适配流程建议</h1><p>如果你的项目 compileSdkVersion 已经升级到 29，那现在就可以开启 Force Dark 适配暗黑模式了。但很多项目要升级到 29 还有一段路要走，我们有没有办法提前适配呢？</p>
<h2 id="Debug-Force-Dark"><a href="#Debug-Force-Dark" class="headerlink" title="Debug Force Dark"></a>Debug Force Dark</h2><p>回到我们开始看源码的地方:</p>
<pre><code>boolean forceDarkAllowedDefault = SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
        &amp;&amp; a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
</code></pre>
<p>当取不到 Theme_forceDarkAllowed 的时候，会取 DEBUG_FORCE_DARK 作为默认值，在哪里可以开启这个 DEBUG_FORCE_DARK 呢？ 在 Android 10 的开发者选项里面，可以发现多了一个这样的选项：</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/1725abd4ae8d9bc0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>这里的「强制启用 SmartDark 功能」就是 DEBUG_FORCE_DARK 的开关，虽然我们看了源码都知道它也没有多智能。 开启后会对所有项目生效，这样就可以提前用 Force Dark 进行适配了。</p>
<h2 id="适配流程"><a href="#适配流程" class="headerlink" title="适配流程"></a>适配流程</h2><p>开启 Force Dark 后大概率会发现一些有问题的图片资源，比如带有固定背景的 icon 等。 如果项目有适配暗黑模式的计划，个人建议可以按以下几步走：</p>
<ol>
<li>开发者选项开启「强制启用 SmartDark」</li>
<li>替换有问题的资源，进行初步适配</li>
<li>compileSdkVersion 升级到 29</li>
<li>开启 Force Dark</li>
<li>和设计师沟通，对部分控件单独适配</li>
</ol>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>使用 DayNight 主题可以实现暗黑模式的适配，但这种方法在实际项目中可操作性不高。 Android 10 新增的暗黑模式特性叫 Force Dark 强制暗黑，只需给主题添加一个允许开启的配置即可。 Force Dark 的实现方式是降低背景亮度，提高字体亮度，本质是对色值进行亮度取反。 最后，在 Android 10 的设备上，可以开启开发者选项中的「强制启用 SmartDark」，提前用 Force Dark 适配。</p>

        </div>

    </div>

    

    

    

    

    

    
<nav class="article-nav">
  
    <a href="/2020/06/14/Androidx%20%E4%B8%8B%20Fragment%20%E6%87%92%E5%8A%A0%E8%BD%BD%E7%9A%84%E6%96%B0%E5%AE%9E%E7%8E%B0/" id="article-nav-newer" class="article-nav-link-wrap">
      <div class="article-nav-caption">下一篇</div>
      <div class="article-nav-title">
        
          Androidx 下 Fragment 懒加载的新实现
        
      </div>
    </a>
  
  
    <a href="/2020/06/12/%E4%BD%BF%E7%94%A8Kotlin%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99%E4%B8%80%E4%B8%AA%E7%8E%B0%E4%BB%A3Android/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-caption">上一篇</div>
      <div class="article-nav-title">使用Kotlin从零开始写一个现代Android</div>
    </a>
  
</nav>


    <section class="share">
        <div class="share-title">分享</div>
        <a class="share-item" target="_blank"
            href="https://twitter.com/share?text=暗黑模式适配 - ApocalypseBlog&url=http://example.com/2020/06/14/Android%2010%20%E6%9A%97%E9%BB%91%E6%A8%A1%E5%BC%8F%E9%80%82%E9%85%8D/">
            <box-icon type='logo' name='twitter'></box-icon>
        </a>
        <a class="share-item" target="_blank"
            href="https://www.facebook.com/sharer.php?title=暗黑模式适配 - ApocalypseBlog&u=http://example.com/2020/06/14/Android%2010%20%E6%9A%97%E9%BB%91%E6%A8%A1%E5%BC%8F%E9%80%82%E9%85%8D/">
            <box-icon name='facebook-square' type='logo' ></box-icon>
        </a>
        <!-- <a class="share-item" target="_blank"
            href="https://service.weibo.com/share/share.php?title=暗黑模式适配 - ApocalypseBlog&url=http://example.com/2020/06/14/Android%2010%20%E6%9A%97%E9%BB%91%E6%A8%A1%E5%BC%8F%E9%80%82%E9%85%8D/&pic=">
            <div class="n-icon n-icon-weibo"></div>
        </a> -->
    </section>

</article>









</div>
                </section>
            </section>

             
            <aside class="sidebar">
            
                
            </aside>
        </div>
    </div>

    <footer class="footer">
    <div class="footer-wave">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="#3c4859" fill-opacity="1" d="M0,160L60,181.3C120,203,240,245,360,240C480,235,600,181,720,186.7C840,192,960,256,1080,261.3C1200,267,1320,213,1380,186.7L1440,160L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path></svg>
    </div>

    <div class="footer-wrap">
        <div class="footer-inner"> 
            ApocalypseBlog &copy; 2021<br>
            Powered By Hexo · Theme By <a href="https://github.com/lh1me/hexo-theme-aomori" target="_blank">Aomori</a>
        </div>
    </div>

</footer>




<script src="/dist/build.js?1.14.0.js"></script>


<script src="/dist/custom.js?1.14.0.js"></script>









</body>

</html>